using System;
using System.CodeDom;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;
using SSOConfig;

namespace SSOConfigTool.CodeGen
{
	/// <summary>
    /// Class for building a typed config model class
	/// </summary>
	public class ConfigClassBuilder
    {
        #region Private instance fields

        private SSOApplication _application;
        private string _className;
        private string _namespace;
        private CodeTypeDeclaration _declaration;
        private CSharpCodeProvider _codeProvider;

        #endregion

        #region Private instance properties

        private CSharpCodeProvider CodeProvider
        {
            get
            {
                if (this._codeProvider == null)
                {
                    this._codeProvider = new CSharpCodeProvider();
                }
                return this._codeProvider;
            }
        }

        private CodeTypeDeclaration Declaration
        {
            get
            {
                if (_declaration == null)
                {
                    _declaration = new CodeTypeDeclaration(this._className);
                    //es - static classes are a C# construct, not a CLR construct so mark as sealed with private constructor:
                    _declaration.TypeAttributes = TypeAttributes.Public | TypeAttributes.Sealed;
                }
                return _declaration;
            }
        }

        private string ClassName
        {
            get { return this._className; }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Constructor with known state
        /// </summary>
        /// <param name="application">Application config store</param>
        /// <param name="targetNamespace">Target namespace for config class</param>
        public ConfigClassBuilder(SSOApplication application, string targetNamespace)
        {
            this._application = application;
            this._className = CodeDomHelper.GetValidId(string.Format("{0}Config", application.Name));
            this._namespace = targetNamespace;
        }

        #endregion

        #region Private instance methods

        private void BuildClass()
		{
            this.AddConstructor();
            this.AddLocalFields();
            this.AddProperties();
            this.AddFieldStruct();
            this.AddApplicationStruct();
		}

        private void AddConstructor()
        {
            CodeConstructor constructor = new CodeConstructor();
            constructor.Attributes = MemberAttributes.Private;
            this.AddMember(constructor);
        }

        private void AddLocalFields()
        {
            //default the cache value to true:          
            this.AddMember(CodeDomHelper.GetField(Fields.CacheSSOAccess, typeof(bool), true, Attributes.PrivateStatic));
            this.AddMember(CodeDomHelper.GetField(Fields.SSOHelper, typeof(SSOHelper), Attributes.PrivateStatic));
            this.AddMember(CodeDomHelper.GetField(Fields.Application, typeof(SSOApplication), Attributes.PrivateStatic));
        }

        public void AddProperties()
        {
            //CacheSSOAccess property determines whether the config is cached or read every time:
            this.AddMember(CodeDomHelper.GetProperty(Properties.CacheSSOAccess, Fields.CacheSSOAccess, typeof(bool), Attributes.PublicStatic));

            //private SSOHelper is lazy loaded:
            this.AddMember(CodeDomHelper.GetProperty(Properties.SSOHelper, Fields.SSOHelper, typeof(SSOHelper), Attributes.PrivateStatic,
                new CodeSnippetExpression(string.Format("if ({0} == null) {{{0} = new SSOHelper();}} return {0}", Fields.SSOHelper)), null));

            //underlying SSO Application is private; reused if loaded & set to cache:
            this.AddMember(CodeDomHelper.GetProperty(Properties.Application, Fields.Application, typeof(SSOApplication), Attributes.PrivateStatic,
                new CodeSnippetExpression(string.Format("if ({0} == null || !{1}) {{{0} = SSOHelper.GetApplication(\"{2}\");}} return {0}", Fields.Application, Fields.CacheSSOAccess, this._application.Name)), null));

            //add in the strongly-typed fields:
            foreach (Field field in this._application.Fields)
            {
                this.AddMember(this.GetConfigProperty(field));
            }

        }

        protected virtual void AddFieldStruct()
        {
            //create new struct:
            CodeTypeDeclaration propertyNameStruct = new CodeTypeDeclaration("Field");
            propertyNameStruct.IsStruct = true;
            //add column names as constants:
            foreach (Field field in this._application.Fields)
            {
                propertyNameStruct.Members.Add(CodeDomHelper.GetStringConstant(field.Name, field.Name));
            }
            //add in comments:
            propertyNameStruct.Comments.AddRange(CodeDomHelper.GetSummaryComments("Field Name struct"));
            //return:
            this.Declaration.Members.Add(propertyNameStruct);
        }

        protected virtual void AddApplicationStruct()
        {
            //create new struct:
            CodeTypeDeclaration applicationStruct = new CodeTypeDeclaration("Application");
            applicationStruct.IsStruct = true;

            //the struct will hold app level info - name, description etc.:
            Dictionary<string, string> appInfo = new Dictionary<string, string>();
            appInfo.Add("Name", this._application.Name);
            appInfo.Add("Description", this._application.Description);
            appInfo.Add("ContactInfo", this._application.ContactInfo);
            appInfo.Add("UserGroup", this._application.UserGroup);
            appInfo.Add("AdminGroup", this._application.AdminGroup);
            //add column names as constants:
            foreach (KeyValuePair<string, string> entry in appInfo)
            {
                applicationStruct.Members.Add(CodeDomHelper.GetStringConstant(entry.Key, entry.Value));
            }
            //add in comments:
            applicationStruct.Comments.AddRange(CodeDomHelper.GetSummaryComments("Application Info Name struct"));
            //return:
            this.AddMember(applicationStruct);
        }

        private CodeMemberProperty GetConfigProperty(Field configField)
        {
            CodeMemberProperty property = new CodeMemberProperty();
            property.Attributes = MemberAttributes.Public | MemberAttributes.Static;
            property.Name = configField.Name;
            property.Type = new CodeTypeReference(configField.Type);
            //the config properties are read-only & read from the Application:
            property.GetStatements.Add(
                new CodeSnippetExpression(string.Format("return ({0}) {1}.GetFieldValue(\"{2}\")", configField.Type, Properties.Application, configField.Name)));
            
            return property;
        }

        private void AddMember(CodeTypeMember member)
        {
            this.Declaration.Members.Add(member);
        }

        #endregion

        #region Public instance methods

        /// <summary>
        /// Returns C# code for the configuration model
        /// </summary>
        /// <returns>Source code</returns>
        public string GetConfigClassCode()
        {
            CodeNamespace nameSpace = new CodeNamespace(this._namespace);
            this.BuildClass();
            nameSpace.Types.Add(this.Declaration);
            nameSpace.Imports.Add(new CodeNamespaceImport("System"));
            nameSpace.Imports.Add(new CodeNamespaceImport("SSOConfig"));

            StringWriter writer = new StringWriter();
            CodeGeneratorOptions options = new CodeGeneratorOptions();
            options.BracingStyle = "C";

            CodeCompileUnit compileUnit = new CodeCompileUnit();
            compileUnit.Namespaces.Add(nameSpace);

            this.CodeProvider.GenerateCodeFromCompileUnit(compileUnit, writer, options);

            CompilerParameters parameters = new CompilerParameters();
            parameters.GenerateInMemory = true;
            writer.Flush();
            return writer.GetStringBuilder().ToString();
        }

        #endregion

        #region Private structs

        private struct Fields
        {
            public const string CacheSSOAccess = "_cacheSSOAccess";
            public const string SSOHelper = "_ssoHelper";
            public const string Application = "_application";
        }

        private struct Properties
        {
            public const string CacheSSOAccess = "CacheSSOAccess";
            public const string SSOHelper = "SSOHelper";
            public const string Application = "SSOApplication";
        }

        private struct Attributes
        {
            public const MemberAttributes PrivateStatic = MemberAttributes.Private | MemberAttributes.Static;
            public const MemberAttributes PublicStatic = MemberAttributes.Public | MemberAttributes.Static;
        }

        #endregion
    }
}